
"""销售单改造"""

from odoo import models, api, fields, _
from odoo.tools import float_compare
from odoo.exceptions import UserError, ValidationError


class SaleOrder(models.Model):
    _inherit = 'sale.order'

    def get_default_department(self):
        if self.env.user.employee_ids:
            return self.env.user.employee_ids[0].department_id

    nd_department_id = fields.Many2one('hr.department', string="所属部门",
                                       default=get_default_department)
    nd_sale_type = fields.Selection([('ol', '线上'), ('dl', '线下')], string="交易方式")
    nd_transport_type = fields.Selection([('自提', '自提'), ('配送', '配送')],
                                         string="运输方式", default="自提")
    nd_hospital_id = fields.Many2one('nd.hospital', string="所属医院")
    nd_hospital_department = fields.Char('科室')

    @api.multi
    def action_confirm(self):
        """检查批次信息"""
        for sale in self:
            sale._check_lot_quantity()

        res = super().action_confirm()

        for sale in self:
            sale._update_lot_into_move(sale.order_line)     # ugly!
        return res

    def _update_lot_into_move(self, sale_lines):
        """将捆包信息更新入调拨单，在创建调拨时调用此方法，因此这里认定每个销售明细只对应一个stock.move"""
        for sale_line in sale_lines:
            move_id = sale_line.move_ids
            move_line_ids = move_id.move_line_ids

            # 通常在寄售时这个值不为空。sale_lines.order_id.is_consignment是更直观的，但是这个字段在后续模块里
            # 为了不破坏模块化，这里使用这种方式来判断是否寄售，寄售时的出库单不能修改他的库位，其他的则要修改库位为销售明细里选择的库位
            need_location_modify = move_id.move_orig_ids

            for i in range(len(sale_line.nd_lot_lines) - len(move_line_ids)):
                move_line_ids |= self.env['stock.move.line'].create(move_id._prepare_move_line_vals())   # TODO 后续如果有预留，这里徐太需要改动

            for nd_lot_line, move_line in zip(sale_line.nd_lot_lines, move_line_ids):
                data = {'lot_id': nd_lot_line.lot_id.id, 'qty_done': nd_lot_line.qty}
                if not need_location_modify:
                    data['location_id'] = nd_lot_line.location_src_id.id
                move_line.write(data)

    def _check_lot_quantity(self):
        """检查所选的批次数量和预定的销售数量是否匹配
        TODO 这里假定了每个销售明细都必须选择批次，是否存在不需要批次的情况
        """
        self.ensure_one()
        for line in self.order_line:
            if line.product_id.tracking == 'lot' and line.product_uom_qty != line.nd_lot_qty:
                raise ValidationError(f'产品{line.product_id.display_name}的订购'
                                      f'数量是{line.product_uom_qty}，但所选批次的'
                                      f'数量总和是 {line.nd_lot_qty}！')


class StockProductLot(models.Model):
    _inherit = 'stock.production.lot'
    # 原生字段存储数据库，用于筛选
    product_qty = fields.Float('数量', compute='_product_qty', store=True)


class SaleOrderLine(models.Model):
    _inherit = 'sale.order.line'

    nd_lot_lines = fields.One2many('nd.sale.line.lot', 'sale_order_line_id', string="批次明细")
    nd_lot_qty = fields.Float('所选批次的数量', compute="_compute_nd_lot_qty", store=True)
    # 明细行增加位置、批次字段
    location_src_id = fields.Many2one('stock.location', string="出库位置")
    location_src_ids = fields.Many2many('stock.location', string="位置筛选字段", compute='get_lot_location')
    product_id = fields.Many2one('product.product', string='产品', domain=[('sale_ok', '=', True)],
                                 change_default=True, ondelete='restrict')
    lot_id = fields.Many2one('stock.production.lot', string="批次")
    lot_ids = fields.Many2many('stock.production.lot', string="批次筛选字段", compute='get_lot_location')
    lot_qty = fields.Float('批次剩余数量', compute="_line_compute_lot_qty")
    available_qty = fields.Float('批次可用数量', compute="_line_compute_lot_qty")

    @api.depends('lot_id', 'location_src_id')
    def _line_compute_lot_qty(self):
        for order in self:
            lot_id = order.lot_id
            location_src_id = order.location_src_id
            if lot_id and location_src_id:
                # 参考了 env['stock.quant']._get_available_quantity()
                Quant = self.env['stock.quant'].sudo()
                product_id = order.product_id
                quants = Quant._gather(product_id, location_src_id, lot_id=lot_id)
                rounding = product_id.uom_id.rounding

                all_quantity = sum(quants.mapped('quantity'))
                if product_id.tracking == 'none':
                    available_quantity = all_quantity - sum(quants.mapped('reserved_quantity'))
                    avail_quantity = available_quantity if float_compare(available_quantity, 0.0,
                                                                         precision_rounding=rounding) >= 0.0 else 0.0
                else:
                    availaible_quantities = {lot_id: 0.0 for lot_id in
                                             list(set(quants.mapped('lot_id'))) + ['untracked']}
                    for quant in quants:
                        if not quant.lot_id:
                            availaible_quantities['untracked'] += quant.quantity - quant.reserved_quantity
                        else:
                            availaible_quantities[quant.lot_id] += quant.quantity - quant.reserved_quantity
                    avail_quantity = sum([available_quantity for available_quantity in availaible_quantities.values() if
                                          float_compare(available_quantity, 0, precision_rounding=rounding) > 0])
                order.update({
                    'lot_qty': all_quantity,
                    'available_qty': avail_quantity
                })

    @api.depends('product_id', 'location_src_id')
    def get_lot_location(self):
        for res in self:
            product_id = res.product_id
            location_src_id = res.location_src_id
            if product_id and not location_src_id:
                # 查找库存
                quant = self.env['stock.quant'].search([('product_id', '=', product_id.id),
                                                        ('location_id.usage', '=', 'internal')])
                location_id = quant.mapped('location_id')
                # lot_ids = quant.mapped('lot_id')
                # res.lot_ids = [(6, 0, lot_ids.ids)]
                res.location_src_ids = [(6, 0, location_id.ids)]
            elif product_id and location_src_id:
                quant_ids = self.env['stock.quant'].search([('product_id', 'in', product_id.ids),
                                                            ('location_id', 'child_of', location_src_id.id)])
                lot_ids = quant_ids.mapped('lot_id')
                res.lot_ids = [(6, 0, lot_ids.ids)]

    @api.onchange('product_id', 'location_src_id')
    def get_location_lot_domain(self):
        """选择产品返回位置和批次的domain"""
        product_id = self.product_id
        location_src_id = self.location_src_id
        if product_id and not location_src_id:
            # 查找库存
            quant = self.env['stock.quant'].search([('product_id', '=', product_id.id),
                                                    ('location_id.usage', '=', 'internal')])
            location_id = quant.mapped('location_id')
            lot_ids = quant.mapped('lot_id')
            domain_location = [('id', 'in', location_id.ids)]
            domain_lot = [('id', 'in', lot_ids.ids)]
            domain = {'domain': {'location_src_id': domain_location, 'lot_id': domain_lot}}
            self.lot_ids = [(6, 0, lot_ids.ids)]
            self.location_src_ids = [(6, 0, location_id.ids)]
            return domain
        elif product_id and location_src_id:
            quant_ids = self.env['stock.quant'].search([('product_id', 'in', product_id.ids),
                                                        ('location_id', 'child_of', location_src_id.id)])
            lot_ids = quant_ids.mapped('lot_id')
            self.lot_ids = [(6, 0, lot_ids.ids)]
            # , ('product_qty', '>', 0)
            return {'domain': {'lot_id': [('id', 'in', lot_ids.ids)]}}

    @api.multi
    def write(self, vals):
        res = super(SaleOrderLine, self).write(vals)
        if vals.get('product_uom_qty') or vals.get('lot_id'):
            for obj in self:
                obj.sale_line_create_nd_line()
        return res

    @api.model
    def create(self, vals):
        res = super(SaleOrderLine, self).create(vals)
        for obj in res:
            obj.sale_line_create_nd_line()
        return res

    def sale_line_create_nd_line(self):
        """创建原有的出库批次明细"""
        product_id = self.product_id
        tracking = self.product_id.tracking
        location_src_id = self.location_src_id
        lot = self.lot_id
        if not all([product_id, location_src_id]):
            return
        nd_lot_lines = self.env['nd.sale.line.lot']
        if tracking != 'lot':
            # 查询有无明细，有则修改，无则创建
            if self.nd_lot_lines:
                # if self.nd_lot_lines[0].reserved_qty != 0:
                #     raise ValidationError(_('请先取消预留再修改数量'))
                self.nd_lot_lines[0].qty = self.product_uom_qty
            else:
                nd_lot_lines = self.env['nd.sale.line.lot'].create({'sale_order_line_id': self.id,
                                                                    'location_src_id': location_src_id.id,
                                                                    'qty': self.product_uom_qty})
        else:
            if not lot:
                raise ValidationError(_('请填写批次号'))
            if self.nd_lot_lines:
                if self.nd_lot_lines[0].reserved_qty != 0 and self.nd_lot_lines[0].lot_id != lot:
                    raise ValidationError(_('请先取消预留再修改数量或批次'))
                self.nd_lot_lines[0].update({'qty': self.product_uom_qty,
                                             'lot_id': lot.id})
            else:
                nd_lot_lines = self.env['nd.sale.line.lot'].create({'sale_order_line_id': self.id,
                                                                    'location_src_id': location_src_id.id,
                                                                    'lot_id': lot.id,
                                                                    'qty': self.product_uom_qty})
        nd_lot_lines._compute_lot_qty()

    @api.depends('nd_lot_lines.qty')
    def _compute_nd_lot_qty(self):
        for line in self:
            line.nd_lot_qty = sum(line.nd_lot_lines.mapped('qty'))

    def nd_action_show_details(self):
        """ 弹出向导框，方便添加批次 """
        self.ensure_one()
        view = self.env.ref('nd_lot.NDSaleLineLotViewForm')
        return {
            'name': '批次/序列号',
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'sale.order.line',
            'views': [(view.id, 'form')],
            'view_id': view.id,
            'target': 'new',
            'res_id': self.id,
        }

    @api.multi
    def nd_confirm(self):
        return {'type': 'ir.actions.act_window_close'}


class SalrOrderLineLot(models.Model):
    _name = 'nd.sale.line.lot'
    _description = '销售明细的批次行'

    def _get_legal_locations(self):
        return self.env['sale.order']._get_legal_locations(return_raw_domain=True)

    sale_order_line_id = fields.Many2one('sale.order.line', string="销售行")
    # product_id = fields.Many2one('product.product', related="sale_order_line_id.product_id", store=True)
    location_src_id = fields.Many2one('stock.location', string="出库位置", domain=_get_legal_locations)
    lot_id = fields.Many2one('stock.production.lot', string="批次")
    lot_qty = fields.Float('批次剩余数量', compute="_compute_lot_qty", store=True)
    available_qty = fields.Float('批次可用数量', compute="_compute_lot_qty", store=True)
    qty = fields.Float('销售数量')

    @api.onchange('location_src_id', 'sale_order_line_id')
    def onchange_location_src_id(self):
        """根据库位的变化，筛选出此库位上有的批次号"""
        if self.location_src_id and self.sale_order_line_id:
            product_id = self.sale_order_line_id.product_id
            quant_ids = self.env['stock.quant'].search([('product_id', 'in', product_id.ids),
                                                      ('location_id', 'child_of', self.location_src_id.id)])
            lot_ids = quant_ids.mapped('lot_id')
            return {'domain': {'lot_id': [('id', 'in', lot_ids.ids), ('product_qty', '>', 0)]}}

    @api.depends('lot_id', 'location_src_id')
    def _compute_lot_qty(self):
        for order in self:
            if order.lot_id and order.location_src_id:
                # 参考了 env['stock.quant']._get_available_quantity()
                Quant = self.env['stock.quant'].sudo()
                product_id = order.sale_order_line_id.product_id
                lot_id = order.lot_id
                quants = Quant._gather(product_id,  order.location_src_id,  lot_id=lot_id)
                rounding = product_id.uom_id.rounding

                all_quantity = sum(quants.mapped('quantity'))
                if product_id.tracking == 'none':
                    available_quantity = all_quantity - sum(quants.mapped('reserved_quantity'))
                    avail_quantity = available_quantity if float_compare(available_quantity, 0.0,
                                                               precision_rounding=rounding) >= 0.0 else 0.0
                else:
                    availaible_quantities = {lot_id: 0.0 for lot_id in list(set(quants.mapped('lot_id'))) + ['untracked']}
                    for quant in quants:
                        if not quant.lot_id:
                            availaible_quantities['untracked'] += quant.quantity - quant.reserved_quantity
                        else:
                            availaible_quantities[quant.lot_id] += quant.quantity - quant.reserved_quantity
                    avail_quantity = sum([available_quantity for available_quantity in availaible_quantities.values() if
                                    float_compare(available_quantity, 0, precision_rounding=rounding) > 0])
                order.update({
                    'lot_qty': all_quantity,
                    'available_qty': avail_quantity
                })


class Location(models.Model):
    _inherit = 'stock.location'

    unqualified_location = fields.Boolean('是一个不合格品库位?')


class GSPCheck(models.AbstractModel):
    _inherit = 'gsp.business.check'

    def _get_legal_locations_domain(self):
        domains = super()._get_legal_locations_domain()
        domains.append(('unqualified_location', '!=', True))
        return domains


